using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;

namespace Coding.Methods
{
    /// <summary>
    /// Golomb coding
    /// floor(x/M) kodowane unarnie
    /// reszta z dzielenia kodowana gamma
    /// M - parametr do dzielenia
    /// </summary>
    public class GolombCoding : CodingMethod
    {
        private int M; // parametr dzielenia

        public GolombCoding(int Mparam)
        {
            M = Mparam;
        }

        public MyBitVector Compress(List<int> value)
        {
            MyBitVector result = new MyBitVector();
            EliasGammaCoding eliasCoding = new EliasGammaCoding();

            int q;
            int r;

            foreach (int x in value)
            {
                q = (int)Math.Floor( (double)((double)x / (double)M));
                r = x - q * M;

                // koduj iloraz
                for (int i = 0; i < q ; i++)
                    result.Append(false);

                //dodaj jedynke
                result.Append(true);

                //koduj reszte wykorzystujac kodowanie gamma ale wartosc o 1 wieksza bo 0 potrzebujemy
                BitArray compressedRemainder = eliasCoding.Compress(r+1);
                for (int i = 0; i < compressedRemainder.Length; i++)
                    result.Append(compressedRemainder[i]);
            }
            return result;
        }

        public List<int> Decompress(MyBitVector array)
        {
            List<int> result = new List<int>();
            int quotientCounter = 0;
            int remainderLengthCounter = 0;
            int q = 0;
            int r = 0;
            bool readingQuotient = true;
            bool readingRemainderLength = false;
            bool readingRemainderBits = false;

            // w tym forze zrobione w chuj brzydko, mozna przerobic ale mi sie nie chce teraz
            for (int i = 0; i < array.Length; i++)
            {
                // odczytaj najpierw q - iloraz, zakodowany unarnie, potem r - remainder
                if (readingQuotient)
                {
                    if (array[i] == false)
                        quotientCounter++;
                    else
                    {
                        // oznacza ze przeczytalismy jedynke oddzielajaca
                        q = quotientCounter;
                        quotientCounter = 0;
                        readingQuotient = false;
                        readingRemainderLength = true;
                        remainderLengthCounter = 0;
                    }
                }
                else if (readingRemainderLength)
                {
                    if (array[i] == false)
                        remainderLengthCounter++;
                    else
                    {
                        // odczytalismy jedynke juz z remaindera
                        readingRemainderBits = true;
                        readingRemainderLength = false;
                        r = (int)Math.Pow(2.0, remainderLengthCounter);

                        //jesli remainder jest dlugosci 1 to wejdz tutaj, czytaj dalej 
                        if (remainderLengthCounter == 0)
                        {
                            result.Add(q * M + r - 1);
                            readingRemainderBits = false;
                            q = 0;
                            r = 0;
                            readingQuotient = true;
                        }
                    
                    }
                }
                else if (readingRemainderBits)
                {
                    remainderLengthCounter--;

                    if (array[i] == true)
                        r += (int)Math.Pow(2.0, remainderLengthCounter);

                    if (remainderLengthCounter == 0)
                    {
                        result.Add(q * M + r - 1);
                        readingRemainderBits = false;
                        q = 0;
                        r = 0;
                        readingQuotient = true;
                    }
                        
                }
            }
            return result;
        }

    }
}
